package org.tau.workshop2011.preprocessing

import scala.collection.mutable.{HashMap => MutableHashMap, HashSet => MutableHashSet}
import org.tau.workshop2011.parsing.AST._
import java.lang.AssertionError
import org.tau.workshop2011.expressions.Type

/** An expander of sub-widget definitions
 *
 * This object expands definitions which use custom-defined widgets into atomic
 * widgets, by replacing instances of custom widgets with the code defining
 * these widgets. While doing so, it also keeps track of which widgets are used
 * only as top-levels and which widgets are also used as sub-widgets. The
 * program returned will contain only top-level widgets!
 *
 * *This is step 2 of the of the DSL->Scala compilation* and the correct way to
 * use this class is:
 *
 * {{{
 * import org.tau.workshop2011._
 *
 * val code = ...
 * val program = parsing.WidgetParser iParse code
 * val simpleProgram = preprocess.PreProcessingVisitor process program
 * }}}
 */

object PreProcessingVisitor {

  /** Returns a copy of the given program, with macro definitions expanded */
  def process(node: Program) : Program = {
    /* All the widget definitions */
    val defs      = new MutableHashMap[String, Widget]
    val topLevels = new MutableHashSet[String]

    val isNativeWidget  = (name: String) => AtomicWidget isNativeWidget name
    val isCustomWidget  = (name: String) => defs contains name
    val isDefinedWidget = (name: String) => isNativeWidget(name) || isCustomWidget (name)

    /* Go over all the widgets once, to mark toplevel widgets, and identify
     * usage of widgets before they were defined */
    for ((name, widget) <- node.defs) {

      /* Make sure we are not redefining an existing native widget */
      if (isNativeWidget (name))
        throw new Exception ("Can't override the definition of the native widget " + name)

      /* Make sure we are not redefining a custom widget */
      if (isCustomWidget (name))
        throw new Exception ("Redefinition of the widget " + name)

      /* If we reached this part, we can start trying to define the widget */

      /* A widget is considered as a top-level until it's used inside another
        * widget. So add it to the top-level list for now */
      topLevels += name

      /* Make sure that all the widgets that are used are defined, and mark used
       * widgets as non top-levels */
      widget accept new ASTDeepVisitor {
        override def visit(node: AtomicWidget) = {
          val isNative : Boolean = isNativeWidget (node.kind)
          val isCustom : Boolean = isCustomWidget (node.kind)

          if (isNative && isCustom) {
            assert (false) /* We should have caught this error when entering the
                            * definition */
          } else if (isNative) {
          } else if (isCustom) {
            /* A widget being used (inside another widget) is not a top-level */
            if (topLevels remove node.kind)
            {
              /* If this is the first time this widget is marked as non
               * top-level, make sure that this widget does not contain '?'
               * properties, since only top level widgets may have such
               * properties that must be specified by the program */
              node accept new ASTDeepVisitor {
                override def visit(attrNode: InitialAttribute) = {
                  if (attrNode.value == None)
                    throw new Exception("Error: The widget " + node.kind + " is "+
                      "not a top-level (since it's used inside " + name + "), " +
                      "but it contains attributes (" + attrNode.getName + ") that " +
                      "should be initialized by the program!")
                }
              }
            }
           } else {
            throw new Exception ("Error: Using the widget " + node.kind + " before " +
              "it was defined!")
          }
          super.visit(node)
        }
      }

      /* If we passed that without an error, then we should now expand all the
       * definitions of custom widgets that are used */

       val expandedWidget = widget accept new ASTDeepCloneVisitor {
         override def visit(node: AtomicWidget) = {
           assert (isDefinedWidget(node.kind))
           if (isNativeWidget (node.kind))
             super.visit(node)
           else if (isCustomWidget(node.kind)) {
             var w = super.visit(defs(node.kind))

             w = Container.Contain(true, w :: Nil, node.width, node.height)

             if (! node.attributes.isEmpty)
               w = PropertyScope.AddAttributes(w, node.attributes map ((attr) => visit(attr)))

             w
           }
           else
             throw new AssertionError("Code should not be reached")
         }
       }

      /* Now, add this widget to the definitions list */
      defs put (name, expandedWidget.asInstanceOf[Widget])
    }

    /* Finally, return a program containing only the top-level widgets */
    val defsT = defs.filter {case (name,widget) => topLevels contains name}
    /* And wrap these in width x height size */
    new Program (defsT map { case (name, widget) => (name,
      Container.Contain(false, List(widget),
        Some(Variable("width", Type.tInt, false)),
        Some(Variable("height", Type.tInt, false))))
    })
  }

}